Costruire un file PDF


Chi non ritiene che il formato pdf si sia guadagnato un posto tra gli standard di fatto può anche fermare la sua lettura qui. Scherzi a parte, quanti di noi non si sono mai trovati di fronte all'esigenza di dover stampare sul client qualcosa che somigliasse ad un modulo con una precisa impostazione grafica in cui però variavano, magari perché generati run-time o perché estratti da un database, i valori dei vari campi? Qualche esempio: e se volessi stampare una fattura direttamente sulla stampante del cliente, ma che sia identica al formato grafico utilizzato nel commercio tradizionale? E se volessi stampare l'output di una query eseguita sul mio AS/400 in un formato graficamente gradevole?

Generalmente non si pensa ad Abode Acrobat come ad uno degli strumenti possibili per generare dei report graficamente elaborati, ma in cui i valori dei numeri e delle stringhe di testo provengono dalle sorgenti più disparate: ebbene si sbaglia!

Scopo di questo articolo è mostrarvi come, a partire da un modello consistente in un file .pdf in cui sono stati definiti dei campi (tranquilli, sui client e sul server non vi servirà l'acrobat exchange, la cui necessità è limitata solo alla macchina su cui vine creato il file .pdf da usare poi come modello), è possibile far giungere al client una sua versione completata con i valori desiderati. Unico vincolo è che sul client sia installato e funzionante l'Acrobat Reader. Prima di passare ad un poco di teoria ringrazio Ty Button per un suo articolo illuminante senza il quale non sarei qui a scrivervi.

Salve, mi presento: sono il fomato FDF!

Oltre a saper aprire e trattare i file PDF, l'acrobat reader è programmato per interpretare un altro formato: il Forms Data Format. Senza entrare troppo nello specifico, vi basti sapere che con un file fdf è possibile "poggiare" sopra ad un file PDF campi e bottoni con i relativi valori. In origine il formato era nato per utilizzare acrobat come front-end in applicazioni in cui si voleva che le form in cui inserire i dati fossero graficamente gradevoli, ma non è questo l'utilizzo che se ne farà qui. Noi utilizzeremo le sue capacità di chiamare un pdf (indirizzandolo con una URL) ed assegnare ai vari campi in esso presenti i valori da noi "decisi".

Non mi sto dunque prefiggendo di creare run-time un oggetto pdf (cosa molto più complicata, ma soprattutto determinante un carico considerevole per un server), ma un semplice file di testo che, diretto verso il client e indicato come fdf spingerà il browser ad aprire il plug-in Adobe Acrobat Reader che a sua volta recupererà dal server il .pdf "modello" e ne compilererà i campi come indicato nel fdf.

Risultato: grafica perfetta, carico limitato e soprattutto localizzato prevalentemente sul client, "stampabilità" assoluta. Rimane da risolvere un problemino: come creiamo i file fdf attraverso codice asp?

FDF Toolkit for ActiveX: FdFAcX.dll per gli amici.

La Adobe ha creato un pacchetto sdk per gli fdf davvero notevole. Tra i tanti strumenti e componenti forniti ci sono due dll che sono le uniche cose necessarie sul server NT nel nostro caso: fdfacx.dll e fdftk.dll. Per le versioni più aggiornate, per il ToolKit completo e per l'intera documentazione qui.

Basta con le parole, passiamo ai fatti.

  1. Registrate le dll sul vostro server (copiatele in una cartella, per esempio c:\winnt\system32\ ed eseguite regsvr32 FdfAcX.dll: verrà in questo modo registrata anche l'altra dll, fdftk.dll)
  2. Affinché sia possibile inviare direttamente attraverso IIS il formato FDF, come standard output al browser del client, è necessario aggiungere ai tipi MIME conosciuti dal server quello application.vnd.fdf.
    In caso contrario il client (nonostante si sia installato correttamente l'adobe acrobat reader) pur leggendo dal buffer il file fdf, ricevendo dal server un'indicazione di tipo MIME sconosciuto proporrà il "Salva con nome.". Per aggiungere il tipo MIME al server è necessario aggiungere un valore ad una chiave di registro.

    SE NON SI HANNO BEN CHIARI I RISCHI DI UNA TALE OPERAZIONE E NON SI SA PROCEDERE AL BACKUP DI REGISTRO EVITARE QUESTO PUNTO VISTO CHE, COME MOSTRATO NEL PRIMO ESEMPIO, E' POSSIBILE SALVARE IL FILE FDF E PROPORLO IN UNA PAGINA HTML COME UN LINK invece che redirigere direttamente il file fdf sul buffer di IIS.

Se invece sapete ciò che state per fare:

  1. Create una pagina di esempio form.asp in cui spingete l'utente ad inserire una serie di dati.
  2. Aprite sulla macchina in cui avete istallato l'Acrobat Exchange per creare i pdf il file in cui volete che i dati compaiano (se volete importate il documento da una versione cartacea attraverso lo scanner) ed aggiungete i campi che volete con gli strumenti per la definizione delle form. Vedrete che è possibile definire, oltre che il nome del campo, una serie di parametri tra cui quelli che definiscono come i dati devono essere visualizzati (font dimensione etc etc). Annotate i nomi che date ai campi visto che dovrete riferirvi a questi ultimi, oltre che al nome del file pdf, nel codice asp.

OK, ma il codice quando arriva?

Io sto immaginando di ricevere i dati da una form, ma nulla v'impedisce (e ci mancherebbe!) di determinare i valori attraverso una query ad un database o in qualunque altro modo vi faccia comodo. Per non dilungarmi troppo non includo il codice della form con cui invio i dati e non vi descrivo come ho creato il documento pdf con i relativi campi. Potrete scaricare tutti i file necessari per eseguire l'esempio, analizzarli e modificarli come meglio credete, per adesso aguzzate le vostre doti deduttive e leggete i commenti al codice.

Creiamo e salviamo un file fdf dinamicamente sul server: il metodo FDFSaveToFile

<%

Response.Buffer = true

%>

<html>

<head><%

Response.ContentType = "text/html"

%>

<title>Primo esempio: Metodo FDFSaveToFile</title>

</head>

<%

' ** Ricevo I dati che sono stati inseriti nella pagina precedente e

' ** inviati con il metodo post.

ValorePrimoCampo = Request.form("NomePrimoCampo")

ValoreSecondoCampo = Request.form("NomeSecondoCampo")

ValoreTerzoCampo = Request.form("NomeTerzoCampo")

ValoreQuartoCampo = Request.form("NomeQuartoCampo")

ValoreQuintoCampo = Request.form("NomeQuintoCampo")

' ** Valorizzo come meglio credo altre variabili

ValoreSestoCampo = "Questo campo è visibile, ma non stampabile. Istante di generazione della pagina: "&Now()

' ** Creiamo un oggetto fdf ToolKit ActiveX

Set FdfAcx = Server.CreateObject("FdfApp.FdfApp")

' ** Creiamo un documeto fdf

Set myFdf = FdfAcx.FDFCreate

' ** Riempiamo i campi con i relativi valori

myFdf.fdfsetvalue "NomePrimoCampo", ValorePrimoCampo, False

myFdf.fdfsetvalue "NomeSecondoCampo", ValoreSecondoCampo, False

myFdf.fdfsetvalue "NomeTerzoCampo", ValoreTerzoCampo, False

myFdf.fdfsetvalue "NomeQuartoCampo", ValoreQuartoCampo, False

myFdf.fdfsetvalue "NomeQuintoCampo", ValoreQuintoCampo, False

myFdf.fdfsetvalue "NomeSestoCampo", ValoreSestoCampo, False

La funzione FDFSetValue ha la sintassi: Sub FDFSetValue(fieldName As String, newValue As String) dove fieldName è la stringa rappresentante il nome completo del campo (per esempio, employee.name.last) newValue è la stringa da usare come nuovo valore

' ** E' quì che indichiamo, nel fdf che stiamo creando, quale file

' ** pdf dovra' essere richiamato. L'indirizzo deve essere fornito

' ** come URL. Per indirizzamenti in relativo approfondite sulla

' ** documentazione. In questo caso scelgo di salvare il file fdf

' ** sul server e di creare un link su cui l'utente puo' cliccare.

' ** E' anche possibile dirigere direttamente l'fdf creato verso il

' ** buffer. Vi rimando alla documentazione del metodo FDFSavetoBuf

' ** e soprattutto all'esempio successivo.

' ** IL RESTO DEL CODICE NON NECESSITA ALTRE MODIFICE, MA E'

' ** NECESSARIO INDICARE QUI' IL PERCORSO VIRTUALE PRECISO PERCHE'

' ** VENGA RAGGIUNTO IL PDF.

myFDF.fdfSetFile "http://localhost/test/asppdf/test.pdf"

' ** Salvo il file fdf generato come CheckThis.fdf.

' ** Per evitare che due persone possano creare due file fdf con lo

' ** stesso nome dovrete decidere una convenzione o generare parte

' ** del nome in maniera univoca. Facendo finta che due richieste nello

' ** stesso secondo non siano possibili genero il nome come

' ** gGIORNOhORAmMINUTOsSECONDO.fdf il che mi risolve anche eventuali

' ** problemi di caching di Acrobat e del browser.

' ** E' inoltre necessario fornire un percorso "fisico" e che l'utente

' ** IUSR_nomeserver abbia i diritti di scrivere in quel percorso.

' ** Tutti problemi che non nascono se scegliamo di dirigere il fdf

' ** direttamente al buffer, dunque non approfondisco soluzioni piu'

' ** articolate per ripulire dai file creati.

' ** Immaginando di essere in C:\inetpub\wwwroot\test\asppdf

' ** il server.mappath("./") restituirà C:\inetpub\wwwroot\test\asppdf

FisicalPathCompleto = Server.MapPath("./")

NomeFile="g"&Day(Now())&"h"&Hour(Now())&"m"&Minute(Now())&"s"&second(Now())&".fdf"

myFDf.FDFSaveToFile FisicalPathCompleto&"\"&NomeFile

%>

<body>

<%

' ** A questo punto puntiamo il file con il percorso "virtuale" relativo

Response.Write("<a href="&NomeFile&">Per vedere il pdf compilato premi qui</A>")

' ** E infine non scordiamoci di fare un poco di pulizia.

myfdf.fdfclose

set fdfacx = nothing

%>

</body>

</html>

Proviamo invece a spedire sul buffer direttamente il file FDF (è importante che dopo il codice non vi sia niente altro, è per questo che ho inserito un response.end che chiude il flusso; editor intelligenti quali FP98 tenderebbero sicuramente, rieditando la pagina attraverso di loro, ad aggiungere i tag <HTML> <BODY> etc etc, fornendo all'acrobat reader una versione corrotta del fdf e scatenando un errore). Ricordatevi che se volete evitare che sul client compaia il messaggio "salva con nome" bisogna impostare sul server il Mime Type per l'FDF come indicato in precedenza.

<%

' ** tutto il codice deve essere in testa al file asp, dunque non deve

' ** essere preceduto da nulla, altrimenti avremmo il classico errore da

' ** "header già impostati"

Response.buffer=True

Response.ContentType = "application/vnd.fdf"

' ** Ricevo I dati che sono stati inseriti nella pagina precedente e

' ** inviati con il metodo post.

ValorePrimoCampo = Request.form("NomePrimoCampo")

ValoreSecondoCampo = Request.form("NomeSecondoCampo")

ValoreTerzoCampo = Request.form("NomeTerzoCampo")

ValoreQuartoCampo = Request.form("NomeQuartoCampo")

ValoreQuintoCampo = Request.form("NomeQuintoCampo")

' ** Creo dinamicamente altri Valori

ValoreSestoCampo = "Questo campo è visibile, ma non stampabile. Istante di generazione della pagina: "&Now()

' ** Creo un oggetto fdf ToolKit ActiveX

' ** ed, utilizzando il metodo FDFCreate, creo un FDF

Set FdfAcx = Server.CreateObject("FdfApp.FdfApp")

Set FDF = FdfAcx.FDFCreate

' ** Riempio i campi del documento con i valori desiderati

FDF.FDFSetValue "NomePrimoCampo", ValorePrimoCampo, False

FDF.FDFSetValue "NomeSecondoCampo", ValoreSecondoCampo, False

FDF.FDFSetValue "NomeTerzoCampo", ValoreTerzoCampo, False

FDF.FDFSetValue "NomeQuartoCampo", ValoreQuartoCampo, False

FDF.FDFSetValue "NomeQuintoCampo", ValoreQuintoCampo, False

FDF.FDFSetValue "NomeSestoCampo", ValoreSestoCampo, False

' ** Indico al fdf il percorso del file pdf in cui inserire i dati

' ** Sara' l'acrobat sul client a scaricare il pdf e riempirlo come

' ** indicato dal fdf (sono ripetitivo eh?).

' ** IL RESTO DEL CODICE NON NECESSITA ALTRE MODIFICE, MA E'

' ** NECESSARIO INDICARE QUI' IL PERCORSO VIRTUALE PRECISO PERCHE'

' ** VENGA RAGGIUNTO IL PDF.

FDF.FDFSetFile "http://localhost/test/asppdf/test.pdf"

' ** Dirigo l'fdf risultante direttamente sul Buffer verso il browser del client

Response.BinaryWrite FDF.FDFSaveToBuf

FDF.FDFClose

set FdfAcx = nothing

' ** forzo il server ad ignorare qualunque altra stupidagine sia stata

' ** inserita a seguire

Response.end

%>

Concludendo

Mi rendo conto di aver sorvolato su alcune questioni non banali (quali l'utilizzo dei vari tipi di campo per le form nei pdf), ma spero di avervi dato lo spunto per approfondire un nuovo strumento, ed osservando il pdf allegato vi accorgerete in fondo è così.

D'altronde per creare i file pdf è necessario acquistare il pacchetto Adobe Acrobat e, se l'avete fatto, dubito che non vi siate messi a "giocare" con la creazione di form. Le applicazioni di un tale approccio ai pdf completati attraverso gli fdf sono innumerevoli.

Spero solo di avervi convinto che il carico lato server è davvero contenuto e per concludere vi ricordo che tutto quanto abbiamo fatto rimane lato client, squisitamente multipiattaforma, in pieno spirito Acrobat.

Per le reference sugli oggetti e sul FDF ToolKit vi consiglio: FDFToolKit Overview e FDFToolKit Reference (nella sezione Toolkit for ActiveX) che potrete trovare al sito sopra indicato. Il pdf allegato è stato creato con Acrobat, ma non ho testato la sua compatibilità con l'Acrobat Reader 3.

[ Scarica gli esempi relativi all'articolo ]